import { marked } from 'marked' // 解析 markdown    
import hljs from 'highlight.js' // 代码高亮
import { Chat, Settings, Message, FileInfo, OllamaModel, TextGroup } from './types' // 类型
import { CONTEXT_PROMPT_TEMPLATE, TITLE_PROMPT_TEMPLATE } from './prompt' // 提示词

/**
 * 聊天应用
 */
export class ChatApp {
    private chats: Chat[] = [] // 聊天列表
    private currentChatId: string | null = null // 当前聊天ID
    private messageContainer: HTMLElement // 消息容器
    private userInput: HTMLTextAreaElement // 用户输入框
    private sendButton: HTMLButtonElement // 发送按钮
    private chatList: HTMLElement // 聊天列表
    private isProcessing: boolean = false // 是否正在处理
    private overlay: HTMLElement // 遮罩层
    // 默认设置信息
    private settings: Settings = {
        model: 'qwen2.5:0.5b', // 模型
        temperature: 0.7, // 温度
        maxLength: 2000, // 最大长度
        topP: 0.9, // topP
        topK: 40, // topK
        repeatPenalty: 1.1, // 重复惩罚
        baseUrl: 'http://localhost:11434' // 基础URL
    }
    private readonly MAX_FILE_SIZE = 1 * 1024 * 1024 // 最大上传文件的大小
    // 允许上传的文件类型
    private readonly ALLOWED_TYPES = [
        'text/plain', // 纯文本
        'text/markdown', // markdown
        'text/javascript', // javascript
        'text/typescript', // typescript
        'text/html', // html
        'text/css', // css
        'application/json', // json
        'text/yaml', // yaml
        'text/xml' // xml
    ]
    private readonly CHUNK_SIZE = 1000 // 分块大小
    private readonly MAX_CONTEXT_LENGTH = 4000 // 最大上下文长度
    private readonly GROUP_SIMILARITY_THRESHOLD = 0.7 // 文本组相似度阈值
    private readonly MAX_GROUP_SIZE = 5 // 最大文本组大小
    private isModelSwitching: boolean = false // 是否正在切换模型

    constructor() {
        // 获取聊天容器、用户输入框、发送按钮、聊天列表
        this.messageContainer = document.getElementById('messageContainer') as HTMLElement
        this.userInput = document.getElementById('userInput') as HTMLTextAreaElement
        this.sendButton = document.getElementById('sendButton') as HTMLButtonElement
        this.chatList = document.getElementById('chatList') as HTMLElement
        // 创建遮罩层
        this.overlay = document.createElement('div')
        this.overlay.className = 'overlay' // 设置遮罩层样式
        document.body.appendChild(this.overlay) // 将遮罩层添加到 body 中

        // 先检查 Ollama 服务
        this.checkOllamaService().then(() => {
            // 初始化应用
            this.initialize()
            // 加载聊天列表
            this.loadChats()
            // 初始化响应式布局
            this.initializeResponsive()
            // 初始化设置
            this.initializeSettings()
            // 初始化文件上传
            this.initializeFileUpload()
        })
    }

    /**
     * 检查 Ollama 服务
     */
    private async checkOllamaService(): Promise<void> {
        try {
            const response = await fetch(`${this.settings.baseUrl}/api/tags`, {
                method: 'GET',
                headers: {
                    'Content-Type': 'application/json',
                },
            })

            if (!response.ok) {
                throw new Error('Ollama 服务未启动')
            }

            // 如果存在错误模态框，移除它
            const existingErrorModal = document.querySelector('.error-modal')?.parentElement
            if (existingErrorModal) {
                existingErrorModal.remove()
            }

            return Promise.resolve()
        } catch (error) {
            this.showOllamaServiceError()
            return Promise.reject(error)
        }
    }

    /**
     * 显示 Ollama 服务错误
     */
    private showOllamaServiceError(): void {
        // 移除已存在的错误模态框
        const existingErrorModal = document.querySelector('.error-modal')?.parentElement
        if (existingErrorModal) {
            existingErrorModal.remove()
        }

        // 创建错误模态框
        const errorModal = document.createElement('div')
        errorModal.className = 'modal show'
        errorModal.innerHTML = `
            <div class="modal-content error-modal">
                <div class="modal-header">
                    <h2>⚠️ 连接错误</h2>
                </div>
                <div class="modal-body">
                    <p>无法连接到 Ollama 服务。请确保：</p>
                    <ol>
                        <li>已经安装了 Ollama
                            <a href="https://ollama.ai" target="_blank" rel="noopener">
                                (点击这里下载)
                            </a>
                        </li>
                        <li>Ollama 服务已经启动</li>
                    </ol>
                    <div class="settings-group">
                        <label>自定义Ollama地址</label>
                        <input type="text" id="errorUrlInput" value="${this.settings.baseUrl}" placeholder="输入Ollama服务地址">
                        <div class="description">例如: http://localhost:11434</div>
                    </div>
                    <div class="error-actions">
                        <button id="retryButton" class="retry-button">重试连接</button>
                    </div>
                </div>
            </div>
        `

        document.body.appendChild(errorModal)

        // 添加URL输入框事件
        const urlInput = errorModal.querySelector('#errorUrlInput') as HTMLInputElement
        urlInput?.addEventListener('input', () => {
            this.updateBaseUrl(urlInput.value)
        })

        // 添加重试按钮事件
        const retryButton = errorModal.querySelector('#retryButton') as HTMLButtonElement
        retryButton?.addEventListener('click', async () => {
            try {
                retryButton.textContent = '正在重试...'
                retryButton.disabled = true
                await this.checkOllamaService()
                // 如果连接成功，初始化应用
                this.initialize()
                // 加载聊天列表
                this.loadChats()
                // 初始化响应式布局
                this.initializeResponsive()
                // 初始化设置
                await this.initializeSettings()
                // 初始化文件上传
                this.initializeFileUpload()
                // 移除错误模态框
                errorModal.remove()
            } catch (error) {
                console.error('Retry failed:', error)
                retryButton.textContent = '重试连接'
                retryButton.disabled = false
            }
        })
    }

    /**
     * 初始化应用
     * 配置 marked 使用 highlight.js
     * 设置代码高亮
     * 发送按钮事件
     * 按下回车键也触发发送
     * 自动调整输入框高度
     * 新建会话按钮事件
     * 清空会话按钮事件
     */
    private initialize(): void {
        // 配置 marked 使用 highlight.js
        marked.setOptions({
            gfm: true,
            breaks: true
        })

        // 设置代码高亮
        marked.use({
            extensions: [{
                name: 'code',
                renderer(token) {
                    const { text, lang } = token
                    const validLanguage = hljs.getLanguage(lang || '') ? lang : 'plaintext'
                    const highlighted = hljs.highlight(text, {
                        language: validLanguage || 'plaintext'
                    }).value
                    return `<pre><code class="hljs language-${validLanguage}">${highlighted}</code></pre>`
                }
            }]
        })

        // 发送按钮事件
        this.sendButton.addEventListener('click', () => this.handleSend())
        // 按下回车键也触发发送
        this.userInput.addEventListener('keydown', (e: KeyboardEvent) => {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault()
                this.handleSend()
            }
        })

        // 自动调整输入框高度
        this.userInput.addEventListener('input', () => {
            this.userInput.style.height = 'auto'
            this.userInput.style.height = this.userInput.scrollHeight + 'px'
        })

        // 新建会话按钮事件
        document.getElementById('newChatButton')?.addEventListener('click', () => {
            this.createNewChat()
        })

        // 更新清空按钮的事件监听
        document.getElementById('clearAllButton')?.addEventListener('click', () => {
            this.clearAllChats()
        })

        // 添加清空会话按钮事件
        document.getElementById('clearChatsButton')?.addEventListener('click', () => {
            this.clearAllChats()
        })
    }

    /**
     * 初始化响应式布局
     * 菜单按钮点击事件
     * 移动端新建会话按钮点击事件
     * 遮罩层点击事件
     * 添加触摸滑动支持
     */
    private initializeResponsive(): void {
        // 获取菜单按钮、移动端新建会话按钮、侧边栏
        const menuButton = document.getElementById('menuButton')
        const mobileNewChat = document.getElementById('mobileNewChat')
        const sidebar = document.querySelector('.sidebar')

        // 菜单按钮点击事件
        menuButton?.addEventListener('click', () => {
            this.toggleSidebar()
        })

        // 移动端新建会话按钮点击事件
        mobileNewChat?.addEventListener('click', () => {
            this.createNewChat()
            // 如果侧边栏是打开的，则关闭它
            if (sidebar?.classList.contains('show')) {
                this.toggleSidebar(false)
            }
        })

        // 遮罩层点击事件
        this.overlay.addEventListener('click', () => {
            this.toggleSidebar(false)
        })

        // 添加触摸滑动支持
        let touchStartX = 0
        let touchMoveX = 0
        let isSwiping = false

        // 触摸开始事件
        document.addEventListener('touchstart', (e) => {
            touchStartX = e.touches[0].clientX
            isSwiping = touchStartX < 30 || ((sidebar?.classList.contains('show') ?? false) && touchStartX > window.innerWidth - 30)
        }, { passive: true })

        // 触摸移动事件
        document.addEventListener('touchmove', (e) => {
            if (!isSwiping) return
            touchMoveX = e.touches[0].clientX
            const swipeDistance = touchMoveX - touchStartX
            const sidebarElement = sidebar as HTMLElement

            if (!sidebarElement?.classList.contains('show') && swipeDistance > 0) {
                sidebarElement?.style.setProperty('transform', `translateX(${Math.min(swipeDistance - 100, 0)}%)`)
            } else if (sidebarElement?.classList.contains('show') && swipeDistance < 0) {
                sidebarElement?.style.setProperty('transform', `translateX(${Math.max(swipeDistance, -100)}%)`)
            }
        }, { passive: true })

        // 触摸结束事件
        document.addEventListener('touchend', (e) => {
            if (!isSwiping) return
            const touchEndX = e.changedTouches[0].clientX
            const swipeDistance = touchEndX - touchStartX
            const sidebarElement = sidebar as HTMLElement

            if (!sidebarElement?.classList.contains('show') && swipeDistance > 50) {
                this.toggleSidebar(true)
            } else if (sidebarElement?.classList.contains('show') && swipeDistance < -50) {
                this.toggleSidebar(false)
            }

            sidebarElement?.style.removeProperty('transform')
            isSwiping = false
        }, { passive: true })
    }

    /**
     * 切换侧边栏
     * @param show 是否显示
     */
    private toggleSidebar(show?: boolean): void {
        // 获取侧边栏
        const sidebar = document.querySelector('.sidebar')
        // 是否显示
        const shouldShow = show ?? !(sidebar?.classList.contains('show') ?? false)

        if (shouldShow) {
            sidebar?.classList.add('show')
            this.overlay.classList.add('show')
        } else {
            sidebar?.classList.remove('show')
            this.overlay.classList.remove('show')
        }
    }

    /**
     * 发送消息
     */
    private async handleSend(): Promise<void> {
        // 如果有正在处理的消息或正在切换模型，则不发送
        if (this.isProcessing || this.isModelSwitching) return

        // 获取用户输入
        const content = this.userInput.value.trim()
        // 如果用户输入为空，则不发送
        if (!content) return

        // 获取当前聊天
        const currentChat = this.getCurrentChat()
        // 消息内容
        let message = content

        // 先清空输入框
        this.userInput.value = ''
        // 设置输入框高度为自动
        this.userInput.style.height = 'auto'

        // 设置正在处理
        this.isProcessing = true
        // 禁用发送按钮
        this.sendButton.disabled = true

        try {
            // 保存并渲染用户消息
            await this.addMessage({
                content: content,
                role: 'user',
                timestamp: new Date()
            }, true)

            // 构建最终提示词
            const contextContent = currentChat
                ? await this.buildContextContent(currentChat, content)
                : null
            // 如果构建的提示词为null就用原来的问题
            message = contextContent
                ? CONTEXT_PROMPT_TEMPLATE.replace('{context}', contextContent)
                    .replace('{question}', content)
                : content

            // 最后发送 AI 响应
            await this.simulateAIResponse(message)
        } finally {
            // 处理完成后重置状态
            this.isProcessing = false
            this.sendButton.disabled = false
        }
    }

    /**
     * 构建文本提示词
     * @param currentChat 当前的会话
     * @param content 提问的内容
     * @returns 
     */
    private async buildContextContent(currentChat: Chat, content: string) {
        if (currentChat?.vectorStore) {
            const relevantContext = await this.findRelevantContext(content)
            if (relevantContext) {
                return `文件内容：\n${relevantContext}`
            }
        }

        if (currentChat?.fileContext) {
            const { name, type, size, content: fileContent } = currentChat.fileContext
            return `文件信息：
- 名称：${name}
- 类型：${type}
- 大小：${size}

文件内容：
${fileContent}`
        }

        return null
    }

    /**
     * 添加消息
     * @param message 消息
     * @param save 是否保存
     */
    private async addMessage(message: Message, save: boolean = true): Promise<void> {
        const currentChat = this.getCurrentChat()
        if (save && currentChat) {
            if (!currentChat.messages) {
                currentChat.messages = []
            }
            currentChat.messages.push(message)
            this.saveChats()
        }

        // 立即渲染消息到界面
        this.renderMessage(message)
    }

    /**
     * 渲染消息
     * @param message 消息
     */
    private async renderMessage(message: Message): Promise<void> {
        // 创建消息元素
        const messageElement = document.createElement('div')
        messageElement.className = `message ${message.role}-message`

        // 创建内容元素
        const contentDiv = document.createElement('div')
        contentDiv.className = 'message-content'

        // 使用 marked 渲染 Markdown
        const contentHtml = await marked.parse(message.content)
        contentDiv.innerHTML = contentHtml

        // 添加时间戳
        const timeElement = document.createElement('div')
        timeElement.className = 'message-time'
        timeElement.textContent = this.formatTime(message.timestamp)
        contentDiv.appendChild(timeElement)

        messageElement.appendChild(contentDiv)

        // 添加代码块复制按钮
        messageElement.querySelectorAll('pre').forEach(pre => {
            const copyButton = document.createElement('button')
            copyButton.className = 'copy-button'
            copyButton.textContent = '复制代码'
            copyButton.onclick = () => {
                const code = pre.querySelector('code')?.textContent || ''
                navigator.clipboard.writeText(code)
                copyButton.textContent = '已复制'
                copyButton.classList.add('copied')
                setTimeout(() => {
                    copyButton.textContent = '复制代码'
                    copyButton.classList.remove('copied')
                }, 2000)
            }
            pre.appendChild(copyButton)
        })

        this.messageContainer.appendChild(messageElement)
        this.scrollToBottom()
    }

    /**
     * 生成会话标题
     * @param chat 会话
     * @param content 内容
     */
    private async generateChatTitle(
        chat: Chat,
        question: string,
        answer: string
    ): Promise<void> {
        try {
            const response = await fetch(`${this.settings.baseUrl}/api/generate`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    model: this.settings.model,
                    prompt: TITLE_PROMPT_TEMPLATE
                        .replace('{question}', question)
                        .replace('{answer}', answer),
                    stream: false,
                    temperature: 0.1
                })
            })

            if (!response.ok) {
                throw new Error('生成标题失败')
            }

            const data = await response.json()
            console.log(TITLE_PROMPT_TEMPLATE
                .replace('{question}', question)
                .replace('{answer}', answer))
            
            console.log(data.response)
            const title = data.response.trim()

            // 更新会话标题
            chat.title = title

            // 更新UI
            const titleElement = document.querySelector('.current-chat-title')
            if (titleElement) {
                titleElement.textContent = title
            }
            this.renderChatList()
            this.saveChats()

        } catch (error) {
            throw error
        }
    }

    /**
     * 显示打字机效果
     * @returns 打字机元素
     */
    private showTypingIndicator(): HTMLElement {
        const indicator = document.createElement('div')
        indicator.className = 'typing-indicator'
        this.messageContainer.appendChild(indicator)
        this.scrollToBottom()
        return indicator
    }

    /**
     * 发送内容给AI得到响应并处理
     * @param message 发送给AI的内容
     */
    private async simulateAIResponse(message: string): Promise<void> {
        // 设置正在处理
        this.isProcessing = true
        // 禁用发送按钮
        this.sendButton.disabled = true
        // 显示打字机效果
        const indicator = this.showTypingIndicator()

        try {
            // 获取当前会话
            const currentChat = this.getCurrentChat()
            // 如果当前会话为空，则不发送
            if (!currentChat) return

            // 发送请求
            const response = await fetch(`${this.settings.baseUrl}/api/chat`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    model: this.settings.model,
                    messages: [{ role: 'user', content: message }],
                    stream: true,
                    temperature: this.settings.temperature,
                    max_length: this.settings.maxLength,
                    top_p: this.settings.topP,
                    top_k: this.settings.topK,
                    repeat_penalty: this.settings.repeatPenalty
                })
            });
            // 如果请求失败，则抛出错误
            if (!response.ok) {
                throw new Error('请求失败，请检查Ollama配置')
            }
            // 移除打字机效果
            indicator.remove()
            // 初始化完整内容
            let fullContent = ''
            // 创建消息元素
            const messageElement = document.createElement('div')
            messageElement.className = 'message assistant-message'
            this.messageContainer.appendChild(messageElement)
            // 创建内容元素
            const contentDiv = document.createElement('div')
            contentDiv.className = 'message-content'
            messageElement.appendChild(contentDiv)

            // 获取响应体
            const reader = response.body?.getReader()
            if (!reader) throw new Error('没有响应体')

            // 打字延迟
            const typeDelay = 10 // 打字延迟（毫秒）
            // 循环读取响应体
            while (true) {
                const { done, value } = await reader.read()
                if (done) break
                // 解码响应的内容
                const chunk = new TextDecoder().decode(value)
                const lines = chunk.split('\n').filter(line => line.trim())

                for (const line of lines) {
                    try {
                        const data = JSON.parse(line)
                        if (data.message?.content) {
                            // 添加新内容
                            const newContent = data.message.content
                            fullContent += newContent

                            // 使用打字机效果显示新内容
                            const renderedContent = await marked.parse(fullContent)
                            contentDiv.innerHTML = renderedContent

                            // 滚动到底部
                            this.scrollToBottom()

                            // 添加小延迟模拟打字效果
                            await new Promise(resolve => setTimeout(resolve, typeDelay))
                        }
                    } catch (e) {
                        console.error('解析错误:', e)
                    }
                }
            }

            // 消息完成后的处理
            const timeElement = document.createElement('div')
            timeElement.className = 'message-time'
            timeElement.textContent = this.formatTime(new Date())
            contentDiv.appendChild(timeElement)

            // 添加代码块复制按钮
            messageElement.querySelectorAll('pre').forEach(pre => {
                const copyButton = document.createElement('button')
                copyButton.className = 'copy-button'
                copyButton.textContent = '复制代码'
                copyButton.onclick = () => {
                    const code = pre.querySelector('code')?.textContent || ''
                    navigator.clipboard.writeText(code)
                    copyButton.textContent = '已复制'
                    copyButton.classList.add('copied')
                    setTimeout(() => {
                        copyButton.textContent = '复制代码'
                        copyButton.classList.remove('copied')
                    }, 2000)
                }
                pre.appendChild(copyButton)
            })

            // 保存消息到聊天历史
            this.addMessageToHistory(fullContent)

        } catch (error) {
            console.error('调用 Ollama API 出错:', error)
            indicator.remove()
            this.addMessage({
                content: "抱歉，我遇到了一些问题。请稍后再试。",
                role: 'assistant',
                timestamp: new Date()
            })
        } finally {
            // 设置正在处理为false
            this.isProcessing = false
            // 启用发送按钮
            this.sendButton.disabled = false
        }
    }

    /**
     * 设置聊天页面一直显示最新的消息(聊天页面盒子的高度一直显示最新消息)
     */
    private scrollToBottom(): void {
        requestAnimationFrame(() => {
            this.messageContainer.scrollTop = this.messageContainer.scrollHeight
        })
    }

    /**
     * 格式化时间
     * @param date 时间
     * @returns 格式化后的时间
     */
    private formatTime(date: Date): string {
        return date.toLocaleTimeString('zh-CN', {
            hour: '2-digit',
            minute: '2-digit'
        });
    }

    /**
     * 创建新会话
     */
    private async createNewChat(): Promise<void> {
        // 创建新会话
        const chat: Chat = {
            id: Date.now().toString(),
            title: `新会话`,
            messages: [], // 初始化为空数组，不包含欢迎消息
            timestamp: new Date()
        }
        // 将新会话添加到会话列表的最前面
        this.chats.unshift(chat)
        // 设置当前会话ID
        this.currentChatId = chat.id
        // 保存会话列表
        this.saveChats()
        // 更新移动端顶部栏的会话标题
        const titleElement = document.querySelector('.current-chat-title');
        if (titleElement) {
            titleElement.textContent = chat.title
        }

        // 清空消息容器和文件上传状态
        this.clearMessages()
        this.updateFileUploadState(chat)

        // 在移动端关闭侧边栏
        const sidebar = document.querySelector('.sidebar')
        if (window.innerWidth <= 768 && sidebar?.classList.contains('show')) {
            this.toggleSidebar(false)
        }

        // 渲染会话列表
        this.renderChatList()

        // 添加欢迎消息，但不保存到会话历史中
        await this.renderMessage({
            content: "你好，有什么我可以帮助你的吗？",
            role: 'assistant',
            timestamp: new Date()
        })

        // 聚焦到输入框
        this.userInput.focus()
    }

    /**
     * 渲染会话列表
     */
    private renderChatList(): void {
        // 清空会话列表
        this.chatList.innerHTML = ''
        // 按时间排序
        this.chats.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
            .forEach(chat => {
                // 创建会话项
                const chatItem = document.createElement('div')
                // 设置会话项的类名
                chatItem.className = `chat-item${chat.id === this.currentChatId ? ' active' : ''}`
                // 创建标题元素
                const titleSpan = document.createElement('span')
                titleSpan.className = 'chat-title'
                titleSpan.textContent = chat.title

                // 创建操作按钮容器
                const actions = document.createElement('div')
                actions.className = 'chat-actions'

                // 重命名按钮
                const renameButton = document.createElement('button')
                renameButton.className = 'chat-action-button'
                renameButton.innerHTML = '✎';
                renameButton.onclick = (e) => {
                    e.stopPropagation()
                    this.startRenameChat(chat)
                }

                // 删除按钮
                const deleteButton = document.createElement('button')
                deleteButton.className = 'chat-action-button'
                deleteButton.innerHTML = '✕'
                deleteButton.onclick = (e) => {
                    e.stopPropagation()
                    this.deleteChat(chat.id)
                }
                // 将重命名和删除按钮添加到操作按钮容器
                actions.appendChild(renameButton)
                actions.appendChild(deleteButton)
                // 将标题和操作按钮添加到会话项
                chatItem.appendChild(titleSpan)
                chatItem.appendChild(actions)
                // 点击会话项切换到当前会话
                chatItem.onclick = () => this.switchChat(chat.id)
                // 将会话项添加到会话列表
                this.chatList.appendChild(chatItem)
            })
    }

    /**
     * 开始重命名会话
     * @param chat 会话
     */
    private startRenameChat(chat: Chat): void {
        // 创建重命名弹窗
        const modal = document.createElement('div')
        modal.className = 'modal show'
        modal.innerHTML = `
            <div class="modal-content rename-modal">
                <div class="modal-header">
                    <h2>重命名会话</h2>
                </div>
                <div class="modal-body">
                    <input type="text" 
                        class="rename-input" 
                        value="${chat.title}" 
                        placeholder="请输入新的会话名称">
                    <div class="modal-actions">
                        <button class="cancel-button">取消</button>
                        <button class="confirm-button">确认</button>
                    </div>
                </div>
            </div>
        `

        // 将重命名弹窗添加到页面
        document.body.appendChild(modal)

        // 获取输入框和按钮元素
        const input = modal.querySelector('.rename-input') as HTMLInputElement
        const cancelButton = modal.querySelector('.cancel-button')
        const confirmButton = modal.querySelector('.confirm-button')

        // 聚焦到输入框末尾
        input.focus()
        // 设置输入框的选中范围
        input.setSelectionRange(input.value.length, input.value.length)

        // 处理重命名操作
        const handleRename = () => {
            // 获取输入框的值并去除首尾空格
            const newTitle = input.value.trim()
            // 如果新标题不为空，则更新会话标题
            if (newTitle) {
                chat.title = newTitle
                // 更新顶部栏的会话标题
                if (chat.id === this.currentChatId) {
                    const titleElement = document.querySelector('.current-chat-title')
                    if (titleElement) {
                        titleElement.textContent = newTitle;
                    }
                }
                // 保存会话列表
                this.saveChats()
                // 重新渲染会话列表
                this.renderChatList()
            }
            // 关闭重命名弹窗
            modal.remove()
        }

        // 绑定事件
        cancelButton?.addEventListener('click', () => modal.remove())
        confirmButton?.addEventListener('click', handleRename)

        // 支持回车确认
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault()
                handleRename()
            }
            // ESC 关闭
            if (e.key === 'Escape') {
                modal.remove()
            }
        })

        // 点击遮罩层关闭
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.remove()
            }
        })
    }

    /**
     * 删除会话
     * @param chatId 会话ID
     */
    private deleteChat(chatId: string): void {
        // 创建确认对话框
        const confirmModal = document.createElement('div')
        confirmModal.className = 'modal show'
        confirmModal.innerHTML = `
            <div class="modal-content confirm-modal">
                <div class="modal-header">
                    <h2>⚠️ 确认删除</h2>
                </div>
                <div class="modal-body">
                    <p>确定要删除这个会话吗？此操作不可恢复。</p>
                    <div class="confirm-actions">
                        <button class="cancel-button">取消</button>
                        <button class="confirm-button">确认删除</button>
                    </div>
                </div>
            </div>
        `

        document.body.appendChild(confirmModal)

        // 添加按钮事件
        const cancelButton = confirmModal.querySelector('.cancel-button')
        const confirmButton = confirmModal.querySelector('.confirm-button')

        cancelButton?.addEventListener('click', () => {
            confirmModal.remove()
        })

        confirmButton?.addEventListener('click', () => {
            // 找到要删除的会话的索引
            const index = this.chats.findIndex(c => c.id === chatId)
            // 如果索引大于-1，则删除会话
            if (index > -1) {
                // 删除会话
                this.chats.splice(index, 1)
                // 保存会话列表
                this.saveChats()
                // 如果删除的是当前会话，则切换到第一个会话
                if (chatId === this.currentChatId) {
                    if (this.chats.length > 0) {
                        this.switchChat(this.chats[0].id)
                    } else {
                        this.createNewChat()
                    }
                } else {
                    this.renderChatList()
                }
            }
            confirmModal.remove()
        })

        // 点击遮罩层关闭
        confirmModal.addEventListener('click', (e) => {
            if (e.target === confirmModal) {
                confirmModal.remove()
            }
        })
    }
    /**
     * 切换会话
     * @param chatId 会话ID
     */
    private switchChat(chatId: string): void {
        // 设置当前会话ID
        this.currentChatId = chatId;

        // 获取当前会话
        const chat = this.chats.find(c => c.id === chatId);
        if (!chat) return;

        // 重新渲染会话列表
        this.renderChatList();

        // 清空消息容器
        this.messageContainer.innerHTML = '';

        // 首先添加欢迎消息
        this.renderMessage({
            content: "你好，有什么我可以帮助你的吗？",
            role: 'assistant',
            timestamp: new Date()
        });

        // 确保消息数组存在
        if (chat.messages && Array.isArray(chat.messages)) {
            // 按时间顺序渲染所有消息
            chat.messages
                .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())
                .forEach(msg => this.renderMessage(msg));
        }

        // 更新移动端顶部栏的会话标题
        const titleElement = document.querySelector('.current-chat-title');
        if (titleElement) {
            titleElement.textContent = chat.title;
        }

        // 更新文件上传状态
        this.updateFileUploadState(chat);
    }


    /**
     * 更新文件上传状态
     * @param chat 会话
     */
    private updateFileUploadState(chat: Chat): void {
        // 移除现有的文件信息容器
        const existingContainer = document.querySelector('.file-info-container')
        existingContainer?.remove()

        // 重置输入框提示
        this.userInput.placeholder = "输入消息..."

        // 如果当前会话有文件上下文，显示文件信息
        if (chat.fileContext) {
            // 显示文件信息
            this.showFileInfo({
                name: chat.fileContext.name,
                type: chat.fileContext.type,
                size: chat.fileContext.size,
            } as FileInfo)
            // 更新输入框提示词
            this.userInput.placeholder = `已上传文件：${chat.fileContext.name}，请输入你的问题...`
        }
    }

    /**
     * 显示文件信息
     * @param file 文件
     */
    private showFileInfo(file: FileInfo): void {
        // 移除现有的文件信息容器
        const existingContainer = document.querySelector('.file-info-container')
        existingContainer?.remove()

        // 创建新的文件信息容器
        const container = document.createElement('div')
        container.className = 'file-info-container'

        // 获取文件图标
        const getFileIcon = (fileName: string): string => {
            // 获取文件扩展名
            const ext = fileName.split('.').pop()?.toLowerCase()
            // 根据扩展名返回文件图标
            switch (ext) {
                case 'txt': return '📄'
                case 'md': return '📝'
                case 'js': return 'JS'
                case 'ts': return 'TS'
                case 'html': return '🌐'
                case 'css': return '🎨'
                case 'json': return '{ }'
                case 'yaml': return '⚙️'
                case 'xml': return '📋'
                default: return '📄'
            }
        }

        container.innerHTML = `
            <div class="file-icon">${getFileIcon(file.name)}</div>
            <div class="file-info">
                <div class="file-name">${file.name}</div>
                <div class="file-meta">
                    <span class="file-type">${file.type || file.name.split('.').pop()?.toUpperCase()}</span>
                    <span class="file-size">${typeof file.size === 'string' ? file.size : this.formatFileSize(file.size)}</span>
                </div>
            </div>
            <div class="file-actions">
                <button class="file-action-button remove">移除</button>
            </div>
        `

        // 添加移除按钮事件
        const removeButton = container.querySelector('.remove')
        removeButton?.addEventListener('click', () => {
            container.remove()

            // 清理当前会话的文件上下文和向量存储
            const currentChat = this.getCurrentChat()
            if (currentChat) {
                delete currentChat.fileContext
                delete currentChat.vectorStore // 同时删除向量存储
                this.saveChats() // 保存更改
            }

            // 重置输入框提示词
            this.userInput.placeholder = "输入消息..."
        })

        // 将容器添加到页面
        const inputContainer = document.querySelector('.input-container')
        inputContainer?.parentNode?.insertBefore(container, inputContainer)
    }

    private clearMessages(): void {
        if (this.messageContainer) {
            this.messageContainer.innerHTML = '';
        }
    }

    /**
     * 获取当前聊天
     * @returns 
     */
    private getCurrentChat(): Chat | undefined {
        // 通过当前聊天ID获取当前聊天
        return this.chats.find(c => c.id === this.currentChatId)
    }

    /**
     * 保存会话列表
     */
    private saveChats(): void {
        // 将会话列表保存到本地存储
        localStorage.setItem('chats', JSON.stringify(this.chats))
    }

    /**
     * 加载会话列表
     */
    private loadChats(): void {
        // 从本地存储加载会话列表
        const savedChats = localStorage.getItem('chats')
        // 如果会话列表存在，则解析并加载
        if (savedChats) {
            // 解析会话列表
            this.chats = JSON.parse(savedChats)
            // 遍历会话列表，将时间戳转换为Date对象
            this.chats.forEach(chat => {
                chat.timestamp = new Date(chat.timestamp)
                chat.messages.forEach(msg => {
                    msg.timestamp = new Date(msg.timestamp)
                })
            })
            // 如果会话列表不为空，则切换到第一个会话
            if (this.chats.length > 0) {
                this.switchChat(this.chats[0].id)
            } else {
                // 如果会话列表为空，则创建一个新的会话
                this.createNewChat()
            }
        } else {
            // 如果会话列表不存在，则创建一个新的会话
            this.createNewChat()
        }
    }

    /**
     * 初始化设置
     */
    private async initializeSettings(): Promise<void> {
        // 获取设置按钮、模态框、关闭按钮和模型选择元素
        const settingsButton = document.getElementById('settingsButton')
        const modal = document.getElementById('settingsModal')
        const closeButton = modal?.querySelector('.close-button')
        const modelSelect = document.getElementById('modelSelect') as HTMLSelectElement

        // 加载保存的设置
        const savedSettings = localStorage.getItem('settings')
        if (savedSettings) {
            // 如果设置存在，则解析并加载
            this.settings = { ...this.settings, ...JSON.parse(savedSettings) }
        }

        // 获取可用模型列表
        try {
            // 从Ollama API获取模型列表
            const response = await fetch(`${this.settings.baseUrl}/api/tags`)
            if (response.ok) {
                // 解析模型列表
                const data = await response.json()
                const models: OllamaModel[] = data.models || []

                // 清空现有选项
                modelSelect.innerHTML = ''

                // 添加模型选项
                models.forEach(model => {
                    // 创建模型选项
                    const option = document.createElement('option')
                    option.value = model.name
                    option.textContent = model.name
                    // 如果模型名称与当前设置的模型名称相同，则选中该选项
                    if (model.name === this.settings.model) {
                        option.selected = true
                    }
                    // 将模型选项添加到模型选择元素中
                    modelSelect.appendChild(option)
                })
            } else {
                // 如果无法获取模型列表，则抛出错误
                throw new Error('无法获取模型列表')
            }
        } catch (error) {
            console.error('无法获取模型列表:', error)
            modelSelect.innerHTML = '<option value="">无法获取模型列表</option>'
        }

        // 初始化设置值
        this.updateSettingsUI()

        // 设置按钮点击事件
        settingsButton?.addEventListener('click', () => {
            modal?.classList.add('show')
        })

        // 关闭按钮点击事件
        closeButton?.addEventListener('click', () => {
            modal?.classList.remove('show')
        })

        // 点击模态框外部关闭
        modal?.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.classList.remove('show')
            }
        })

        // 模型选择改变事件
        modelSelect?.addEventListener('change', async () => {
            // 如果正在切换模型，则不处理
            if (this.isModelSwitching) return

            // 设置正在切换模型状态
            this.isModelSwitching = true

            // 获取模态框和发送按钮
            const modal = document.getElementById('settingsModal')
            const sendButton = document.getElementById('sendButton') as HTMLButtonElement

            // 禁用发送按钮
            if (sendButton) {
                sendButton.disabled = true
            }

            // 显示切换提示
            const toast = document.createElement('div')
            toast.className = 'toast'
            toast.textContent = '正在切换模型...'
            document.body.appendChild(toast)

            try {
                // 异步验证模型
                const response = await fetch(`${this.settings.baseUrl}/api/generate`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        model: modelSelect.value,
                        prompt: 'test',
                        stream: false
                    })
                })

                if (!response.ok) {
                    throw new Error('模型切换失败')
                }

                // 更新设置
                this.settings.model = modelSelect.value
                this.saveSettings()

                // 更新提示
                toast.textContent = '模型切换成功'
                toast.classList.add('success')

                // 关闭设置模态框
                modal?.classList.remove('show')

            } catch (error) {
                console.error('模型切换失败:', error)
                // 如果失败，回退到之前的模型
                modelSelect.value = this.settings.model
                toast.textContent = '模型切换失败，请重试'
                toast.classList.add('error')
            } finally {
                // 重置状态
                this.isModelSwitching = false
                // 启用发送按钮
                if (sendButton) {
                    sendButton.disabled = false
                }

                // 3秒后移除提示
                setTimeout(() => {
                    toast.classList.add('hide')
                    setTimeout(() => toast.remove(), 300)
                }, 3000)
            }
        })

        // 温度滑块改变事件
        const temperatureSlider = document.getElementById('temperatureSlider') as HTMLInputElement
        const temperatureValue = document.getElementById('temperatureValue')
        temperatureSlider?.addEventListener('input', () => {
            const value = parseInt(temperatureSlider.value) / 100
            this.settings.temperature = value
            if (temperatureValue) {
                temperatureValue.textContent = value.toFixed(2)
            }
            this.saveSettings()
        })

        // 最大长度滑块改变事件
        const maxLengthSlider = document.getElementById('maxLengthSlider') as HTMLInputElement
        const maxLengthValue = document.getElementById('maxLengthValue')
        maxLengthSlider?.addEventListener('input', () => {
            const value = parseInt(maxLengthSlider.value)
            this.settings.maxLength = value
            if (maxLengthValue) {
                maxLengthValue.textContent = value.toString()
            }
            this.saveSettings()
        })

        // Top P 滑块事件
        const topPSlider = document.getElementById('topPSlider') as HTMLInputElement
        const topPValue = document.getElementById('topPValue')
        topPSlider?.addEventListener('input', () => {
            const value = parseInt(topPSlider.value) / 100
            this.settings.topP = value
            if (topPValue) {
                topPValue.textContent = value.toFixed(2)
            }
            this.saveSettings()
        })

        // Top K 滑块事件
        const topKSlider = document.getElementById('topKSlider') as HTMLInputElement
        const topKValue = document.getElementById('topKValue')
        topKSlider?.addEventListener('input', () => {
            const value = parseInt(topKSlider.value)
            this.settings.topK = value
            if (topKValue) {
                topKValue.textContent = value.toString()
            }
            this.saveSettings()
        })

        // 重复惩罚滑块事件
        const repeatPenaltySlider = document.getElementById('repeatPenaltySlider') as HTMLInputElement
        const repeatPenaltyValue = document.getElementById('repeatPenaltyValue')
        repeatPenaltySlider?.addEventListener('input', () => {
            const value = parseInt(repeatPenaltySlider.value) / 100
            this.settings.repeatPenalty = value
            if (repeatPenaltyValue) {
                repeatPenaltyValue.textContent = value.toFixed(2)
            }
            this.saveSettings()
        })

        // 在 initializeSettings 方法中添加对 baseUrlInput 的事件监听
        const baseUrlInput = document.getElementById('baseUrlInput') as HTMLInputElement
        baseUrlInput?.addEventListener('input', () => {
            this.updateBaseUrl(baseUrlInput.value)
        })
    }

    /**
     * 更新设置UI
     */
    private updateSettingsUI(): void {
        // 获取模型选择元素、温度滑块、温度值、最大长度滑块、最大长度值、Top P滑块、Top P值、重复惩罚滑块、重复惩罚值
        const modelSelect = document.getElementById('modelSelect') as HTMLSelectElement
        const temperatureSlider = document.getElementById('temperatureSlider') as HTMLInputElement
        const temperatureValue = document.getElementById('temperatureValue')
        const maxLengthSlider = document.getElementById('maxLengthSlider') as HTMLInputElement
        const maxLengthValue = document.getElementById('maxLengthValue')
        const topPSlider = document.getElementById('topPSlider') as HTMLInputElement
        const topPValue = document.getElementById('topPValue')
        const repeatPenaltySlider = document.getElementById('repeatPenaltySlider') as HTMLInputElement
        const repeatPenaltyValue = document.getElementById('repeatPenaltyValue')

        // 更新设置UI
        if (modelSelect) modelSelect.value = this.settings.model
        if (temperatureSlider) temperatureSlider.value = (this.settings.temperature * 100).toString()
        if (temperatureValue) temperatureValue.textContent = this.settings.temperature.toFixed(2)
        if (maxLengthSlider) maxLengthSlider.value = this.settings.maxLength.toString()
        if (maxLengthValue) maxLengthValue.textContent = this.settings.maxLength.toString()
        if (topPSlider) topPSlider.value = (this.settings.topP * 100).toString()
        if (topPValue) topPValue.textContent = this.settings.topP.toFixed(2)
        if (repeatPenaltySlider) repeatPenaltySlider.value = (this.settings.repeatPenalty * 100).toString()
        if (repeatPenaltyValue) repeatPenaltyValue.textContent = this.settings.repeatPenalty.toFixed(2)
    }

    /**
     * 保存设置
     */
    private saveSettings(): void {
        // 将设置保存到本地存储
        localStorage.setItem('settings', JSON.stringify(this.settings))
    }

    /**
     * 添加消息到历史记录
     * @param content 消息内容
     */
    private addMessageToHistory(content: string): void {
        const currentChat = this.getCurrentChat()
        if (currentChat) {
            // 将消息添加到历史记录
            const newMessage: Message = {
                content,
                role: 'assistant',
                timestamp: new Date()
            }
            currentChat.messages.push(newMessage)

            // 如果这是AI对第一个用户问题的回答，则生成标题
            const messages = currentChat.messages
            if (messages.length === 2 &&
                messages[0].role === 'user' &&
                messages[1].role === 'assistant') {
                // 使用 Promise.resolve().then() 确保标题生成在微任务队列中异步执行
                Promise.resolve().then(() => {
                    this.generateChatTitle(
                        currentChat,
                        messages[0].content, // 用户问题
                        messages[1].content  // AI回答
                    ).catch(error => {
                        console.error('生成标题失败:', error)
                        // 如果生成失败，使用默认标题
                        currentChat.title = messages[0].content.slice(0, 15) +
                            (messages[0].content.length > 15 ? '...' : '')
                        this.renderChatList()
                        this.saveChats()
                    })
                })
            }

            // 保存会话列表
            this.saveChats()
        }
    }

    /**
     * 清空所有会话
     */
    private clearAllChats(): void {
        // 创建确认对话框
        const confirmModal = document.createElement('div')
        confirmModal.className = 'modal show'
        confirmModal.innerHTML = `
            <div class="modal-content confirm-modal">
                <div class="modal-header">
                    <h2>⚠️ 确认清空</h2>
                </div>
                <div class="modal-body">
                    <p>确定要清空所有会话吗？此操作不可恢复。</p>
                    <div class="confirm-actions">
                        <button class="cancel-button">取消</button>
                        <button class="confirm-button">确认清空</button>
                    </div>
                </div>
            </div>
        `

        document.body.appendChild(confirmModal)

        // 添加按钮事件
        const cancelButton = confirmModal.querySelector('.cancel-button')
        const confirmButton = confirmModal.querySelector('.confirm-button')

        cancelButton?.addEventListener('click', () => {
            confirmModal.remove()
        })

        confirmButton?.addEventListener('click', () => {
            this.chats = []
            this.saveChats()
            this.createNewChat()

            // 在移动端关闭侧边栏
            const sidebar = document.querySelector('.sidebar')
            if (window.innerWidth <= 768 && sidebar?.classList.contains('show')) {
                this.toggleSidebar(false)
            }

            // 显示成功提示
            const successToast = document.createElement('div')
            successToast.className = 'toast';
            successToast.textContent = '已清空所有会话'
            document.body.appendChild(successToast)

            // 3秒后移除提示
            setTimeout(() => {
                successToast.classList.add('hide')
                setTimeout(() => successToast.remove(), 300);
            }, 3000)

            confirmModal.remove()
        })

        // 点击遮罩层关闭
        confirmModal.addEventListener('click', (e) => {
            if (e.target === confirmModal) {
                confirmModal.remove()
            }
        })
    }

    /**
     * 初始化文件上传
     */
    private initializeFileUpload(): void {
        // 获取上传按钮、文件输入框、拖拽覆盖层、聊天容器
        const uploadButton = document.getElementById('uploadButton')
        const fileInput = document.getElementById('fileInput') as HTMLInputElement
        const dragOverlay = document.querySelector('.drag-overlay') as HTMLElement
        const chatContainer = document.querySelector('.chat-container') as HTMLElement

        // 上传按钮点击事件
        uploadButton?.addEventListener('click', () => {
            fileInput?.click()
        });

        // 文件选择事件
        fileInput?.addEventListener('change', (e) => {
            const files = (e.target as HTMLInputElement).files
            if (!files?.length) return

            this.handleFile(files[0])
            fileInput.value = '' // 清空文件输入以允许重复上传
        })

        // 拖拽相关事件
        chatContainer.addEventListener('dragenter', (e) => {
            e.preventDefault()
            dragOverlay.classList.add('active')
        })

        dragOverlay.addEventListener('dragleave', (e) => {
            e.preventDefault()
            dragOverlay.classList.remove('active')
        })

        dragOverlay.addEventListener('dragover', (e) => {
            e.preventDefault()
        })

        dragOverlay.addEventListener('drop', (e) => {
            e.preventDefault()
            dragOverlay.classList.remove('active')

            const files = e.dataTransfer?.files
            if (!files?.length) return

            this.handleFile(files[0])
        })
    }

    /**
     * 处理文件
     * @param file 文件
     */
    private async handleFile(file: File): Promise<void> {
        // 检查文件大小
        if (file.size > this.MAX_FILE_SIZE) {
            this.showError('文件大小不能超过 1MB')
            return
        }

        // 检查文件类型
        if (!this.ALLOWED_TYPES.includes(file.type) &&
            !file.name.match(/\.(txt|md|js|ts|html|css|json|yaml|xml)$/)) {
            this.showError('不支持的文件类型')
            return
        }

        try {
            // 显示文件信息
            this.showFileInfo({
                name: file.name,
                type: file.type || file.name.split('.').pop()?.toUpperCase() || '',
                size: this.formatFileSize(file.size)
            })

            // 读取文件内容
            const content = await file.text()

            // 文本分块
            const chunks = this.splitTextIntoChunks(content)

            // 获取文本向量嵌入
            const vectors = await this.getTextEmbeddings(chunks)

            // 对文本块进行分组
            const groups = await this.groupTextChunks(chunks, vectors)

            // 获取当前聊天
            const currentChat = this.getCurrentChat()
            // 如果当前聊天存在，则将向量存储添加到聊天
            if (currentChat) {
                currentChat.vectorStore = {
                    groups,
                    texts: chunks,
                    metadata: chunks.map((_, i) => ({
                        index: i,
                        filename: file.name,
                        type: file.type
                    }))
                }

                // 保存原始文件上下文
                currentChat.fileContext = {
                    name: file.name,
                    type: file.type || file.name.split('.').pop()?.toUpperCase() || '',
                    size: this.formatFileSize(file.size),
                    content: content
                }

                // 保存会话列表
                this.saveChats()
            }

            // 更新输入框提示
            this.userInput.placeholder = `已上传文件：${file.name}，请输入你的问题...`

        } catch (error) {
            console.error('文件处理失败:', error)
            this.showError('文件处理失败')
        }
    }

    /**
     * 分割文本
     * @param text 文本
     * @returns 文本块
     */
    private splitTextIntoChunks(text: string): string[] {
        // 首先按段落分割
        const paragraphs = text.split(/\n\s*\n/)
        // 初始化文本块
        const chunks: string[] = []
        // 当前文本块
        let currentChunk = ''

        // 遍历每个段落
        for (const paragraph of paragraphs) {
            // 如果当前段落本身就超过块大小，需要进一步分割
            if (paragraph.length > this.CHUNK_SIZE) {
                // 将过长的段落添加到chunks中
                const paragraphChunks = this.splitLongParagraph(paragraph)
                chunks.push(...paragraphChunks)
                continue
            }

            // 如果添加当前段落会超过块大小，先保存当前块，然后开始新块
            if (currentChunk.length + paragraph.length > this.CHUNK_SIZE) {
                if (currentChunk) {
                    chunks.push(currentChunk.trim())
                }
                currentChunk = paragraph
            } else {
                // 否则，将段落添加到当前块
                currentChunk = currentChunk
                    ? currentChunk + '\n\n' + paragraph
                    : paragraph
            }
        }

        // 添加最后一个块
        if (currentChunk) {
            chunks.push(currentChunk.trim())
        }

        return chunks
    }

    /**
     * 分割长段落
     * @param paragraph 段落
     * @returns 文本块
     */
    private splitLongParagraph(paragraph: string): string[] {
        // 初始化文本块
        const chunks: string[] = []
        // 当前索引
        let currentIndex = 0

        while (currentIndex < paragraph.length) {
            // 找到一个合适的断句点
            let endIndex = currentIndex + this.CHUNK_SIZE
            // 如果结束索引小于段落长度，则向后查找最近的句号或换行符
            if (endIndex < paragraph.length) {
                const nextSentence = paragraph.indexOf('. ', endIndex)
                const nextNewline = paragraph.indexOf('\n', endIndex)

                // 如果句号在换行符之前，则以句号为断句点
                if (nextSentence !== -1 && (nextNewline === -1 || nextSentence < nextNewline)) {
                    endIndex = nextSentence + 1
                } else if (nextNewline !== -1) {
                    endIndex = nextNewline
                }
            } else {
                endIndex = paragraph.length
            }

            chunks.push(paragraph.slice(currentIndex, endIndex).trim())
            currentIndex = endIndex
        }

        return chunks
    }

    /**
     * 获取文本向量嵌入
     * @param texts 文本
     * @returns 向量
     */
    private async getTextEmbeddings(texts: string[]): Promise<number[][]> {
        const vectors: number[][] = []

        for (const text of texts) {
            try {
                // 发送请求获取向量
                const response = await fetch(`${this.settings.baseUrl}/api/embeddings`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        model: this.settings.model,
                        prompt: text
                    })
                })

                if (!response.ok) {
                    throw new Error('获取向量失败')
                }

                // 获取向量
                const data = await response.json()
                // 添加到向量数组
                vectors.push(data.embedding)

            } catch (error) {
                console.error('获取向量失败:', error)
                throw error
            }
        }

        return vectors
    }

    /**
     * 格式化文件大小
     * @param bytes 字节
     * @returns 文件大小
     */
    private formatFileSize(bytes: number): string {
        // 如果字节小于1024，则返回字节 
        if (bytes < 1024) return bytes + ' B'
        // 如果字节小于1024*1024，则返回KB
        if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
        // 否则返回MB
        return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
    }
    /**
     * 显示错误信息
     * @param message 错误信息
     */
    private showError(message: string): void {
        // 创建错误提示
        const errorToast = document.createElement('div')
        errorToast.className = 'toast error'
        errorToast.textContent = message
        document.body.appendChild(errorToast)

        setTimeout(() => {
            errorToast.classList.add('hide')
            setTimeout(() => errorToast.remove(), 300)
        }, 3000)
    }

    /**
     * 对文本块进行分组
     * @param chunks 文本块
     * @param vectors 向量
     * @returns 分组
     */
    private async groupTextChunks(chunks: string[], vectors: number[][]): Promise<TextGroup[]> {
        // 初始化分组
        const groups: TextGroup[] = []
        // 已使用索引
        const usedIndices = new Set<number>()

        // 遍历每个文本块
        for (let i = 0; i < chunks.length; i++) {
            // 如果已使用索引，则跳过
            if (usedIndices.has(i)) continue

            // 创建当前组
            const currentGroup: TextGroup = {
                centroid: vectors[i],
                chunks: [{
                    text: chunks[i],
                    vector: vectors[i],
                    metadata: { index: i }
                }]
            }
            // 添加已使用索引
            usedIndices.add(i)

            // 查找相似的文本块
            for (let j = i + 1; j < chunks.length; j++) {
                // 如果已使用索引，则跳过
                if (usedIndices.has(j)) continue

                // 计算相似度
                const similarity = this.cosineSimilarity(vectors[i], vectors[j]);
                // 如果相似度大于阈值，则将文本块添加到当前组
                if (similarity >= this.GROUP_SIMILARITY_THRESHOLD &&
                    currentGroup.chunks.length < this.MAX_GROUP_SIZE) {
                    currentGroup.chunks.push({
                        text: chunks[j],
                        vector: vectors[j],
                        metadata: { index: j }
                    })
                    // 添加已使用索引
                    usedIndices.add(j)
                }
            }

            // 更新组的质心
            if (currentGroup.chunks.length > 1) {
                currentGroup.centroid = this.calculateCentroid(
                    currentGroup.chunks.map(c => c.vector)
                )
            }

            // 添加到分组
            groups.push(currentGroup)
        }

        return groups
    }

    /**
     * 计算质心
     * @param vectors 向量
     * @returns 质心
     */
    private calculateCentroid(vectors: number[][]): number[] {
        // 维度
        const dimension = vectors[0].length
        // 初始化质心
        const centroid = new Array(dimension).fill(0)

        for (const vector of vectors) {
            for (let i = 0; i < dimension; i++) {
                // 累加向量
                centroid[i] += vector[i]
            }
        }

        for (let i = 0; i < dimension; i++) {
            // 计算质心
            centroid[i] /= vectors.length
        }

        return centroid
    }

    /**
     * 查找相关上下文
     * @param query 查询内容
     * @returns 相关上下文
     */
    private async findRelevantContext(query: string): Promise<string> {
        // 获取当前聊天
        const currentChat = this.getCurrentChat()
        // 如果当前聊天没有向量存储，则返回空字符串
        if (!currentChat?.vectorStore?.groups) {
            return ''
        }

        try {
            // 获取查询向量
            const queryVector = (await this.getTextEmbeddings([query]))[0]

            // 首先找到最相关的组
            const groupSimilarities = currentChat.vectorStore.groups.map(group => ({
                // 计算组与查询向量的相似度
                similarity: this.cosineSimilarity(queryVector, group.centroid),
                // 组
                group
            }))

            // 按相似度排序并选择最相关的组
            const topGroups = groupSimilarities
                .sort((a, b) => b.similarity - a.similarity)
                .slice(0, 2) // 选择前两个最相关的组

            // 在选中的组内找到最相关的文本块
            const relevantChunks = []
            // 遍历每个组
            for (const { group } of topGroups) {
                // 计算组内每个文本块与查询向量的相似度
                const chunkSimilarities = group.chunks.map(chunk => ({
                    similarity: this.cosineSimilarity(queryVector, chunk.vector),
                    text: chunk.text
                }))
                    .filter(chunk => chunk.similarity > 0.3)
                    .sort((a, b) => b.similarity - a.similarity)
                    .slice(0, 2) // 每组选择最相关的两个块

                // 将相关文本块添加到结果数组   
                relevantChunks.push(...chunkSimilarities)
            }
            // 如果相关文本块为空，则返回空字符串
            if (relevantChunks.length === 0) {
                return '未找到相关内容'
            }

            // 合并相关文本块，确保不超过最大长度
            let contextText = ''
            let totalLength = 0

            // 遍历每个相关文本块
            for (const chunk of relevantChunks) {
                // 计算文本块的文本
                const chunkText = `[相关度: ${(chunk.similarity * 100).toFixed(1)}%]\n${chunk.text}\n\n`
                // 如果文本块长度超过最大长度，则停止
                if (totalLength + chunkText.length > this.MAX_CONTEXT_LENGTH) {
                    break
                }
                // 将文本块添加到结果文本
                contextText += chunkText
                // 更新总长度
                totalLength += chunkText.length
            }

            return contextText.trim()

        } catch (error) {
            console.error('没有找到相关内容:', error)
            return ''
        }
    }

    /**
     * 计算余弦相似度
     * @param a 向量a
     * @param b 向量b
     * @returns 相似度
     */
    private cosineSimilarity(a: number[], b: number[]): number {
        // 计算点积
        const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0)
        // 计算向量a的模
        const normA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0))
        // 计算向量b的模
        const normB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0))
        // 返回相似度
        return dotProduct / (normA * normB)
    }

    /**
     * 添加一个方法来更新 baseUrl
     * @param newUrl 新的基础URL
     */
    public updateBaseUrl(newUrl: string): void {
        this.settings.baseUrl = newUrl
        this.saveSettings()
    }
}